grammar a;

options {
backtrack=true;
output=template;
language=Java;
}

scope slist {
    List locals; // must be defined one per semicolon
    List stats;
}

/*
@slist::init {
    locals = new ArrayList();
    stats = new ArrayList();
}
*/
@header {
  package lk.uom.cse.adarchi.dlxasm;
  import org.antlr.stringtemplate.*;
}

@lexer::header
{
package lk.uom.cse.adarchi.dlxasm;
}

program
scope {
  List globals;
  List functions;

}
@init {
  $program::globals = new ArrayList();
  $program::functions = new ArrayList();

}
    :   declaration+
        -> program(globals={$program::globals},functions={$program::functions})
    ;

declaration
    :   variable   {$program::globals.add($variable.st);}
    |   f=function {$program::functions.add($f.st);}
    ;

// ack is $function.st ambig?  It can mean the rule's dyn scope or
// the ref in this rule.  Ack.

variable
    :   type declarator ';'
        -> {$function.size()>0 && $function::name==null}?
           globalVariable(type={$type.st},name={$declarator.st})
        -> variable(type={$type.st},name={$declarator.st})
    ;

declarator
    :   ID -> {new StringTemplate($ID.text)}
    ;

function
scope {
    String name;
    int loopcount;
}
scope slist;
@init {
  $slist::locals = new ArrayList();
  $slist::stats = new ArrayList();
}
    :   type ID {$function::name=$ID.text;}
        '(' ( p+=formalParameter ( ',' p+=formalParameter )* )? ')'
        block
	->{"main".equalsIgnoreCase($function::name)}?
           main_function(type={$type.st}, name={"main"},
                    locals={$slist::locals},
                    stats={$slist::stats},
                    args={$p})
        -> function(type={$type.st}, name={$function::name},
                    locals={$slist::locals},
                    stats={$slist::stats},
                    args={$p})
    ;

formalParameter
    :   type declarator 
        -> parameter(type={$type.st},name={$declarator.st})
    ;

type
    :   'int'  -> type_int()
    |   'char' -> type_char()
    |   ID     -> type_user_object(name={$ID.text})
    ;

block
    :  '{'
       ( variable {$slist::locals.add($variable.st);} )*
       ( stat {$slist::stats.add($stat.st);})*
       '}'
    ;

stat
scope slist;
@init {
  $slist::locals = new ArrayList();
  $slist::stats = new ArrayList();
}
    : forStat -> {$forStat.st}
    | whileStat -> {$whileStat.st}
    | dowhileStat -> {$dowhileStat.st}
    | expr ';' -> statement(expr={$expr.st})
    | block -> statementList(locals={$slist::locals}, stats={$slist::stats})
    | assignStat ';' -> {$assignStat.st}
    | ';' -> {new StringTemplate(";")}
    ;

forStat
scope slist;
@init {
  $slist::locals = new ArrayList();
  $slist::stats = new ArrayList();
}
    :   'for' '(' e1=assignStat ';' e2=expr ';' e3=assignStat ')' block {$function::loopcount= $function::loopcount + 1;}
        -> forLoop(name={$function::name},lcount={$function::loopcount},e1={$e1.st},e2={$e2.st},e3={$e3.st},
                   locals={$slist::locals}, stats={$slist::stats})
    ;

whileStat
scope slist;
@init {
  $slist::locals = new ArrayList();
  $slist::stats = new ArrayList();
}
    :   'while' '(' e2=expr ')' block {$function::loopcount= $function::loopcount + 1;}
        -> whileLoop(name={$function::name},lcount={$function::loopcount},e2={$e2.st},
                   locals={$slist::locals}, stats={$slist::stats})
    ;

dowhileStat
scope slist;
@init {
  $slist::locals = new ArrayList();
  $slist::stats = new ArrayList();
}
    :   'do' block 'while' '(' e2=expr ')' ';' {$function::loopcount= $function::loopcount + 1;}
        -> dowhileLoop(name={$function::name},lcount={$function::loopcount},e2={$e2.st},
                   locals={$slist::locals}, stats={$slist::stats})
    ;

assignStat
    :   a=ID  '='  
    	( b=INT -> assignID(lhs={$a.text}, rhs={$b.text})
    	|   expr -> assign(lhs={$a.text}, rhs={$expr.st}) )
    ;

expr:   condExpr -> {$condExpr.st}
    ;

condExpr
    :   a=aexpr
        (   (  '==' b=aexpr -> equals(left={$a.st},right={$b.st})
            |  '<' b=aexpr   -> lessThan(left={$a.st},right={$b.st})
            )
        |   -> {$a.st} // else just aexpr
        )
    ;

aexpr
    :	(a=atom -> {$a.st})
        ( '+' b=INT -> add(left={$aexpr.st}, right={$b.text}) )*
    ;


atom
    : ID -> refVar(id={$ID.text})
    | INT -> iconst(value={$INT.text})
    | '(' expr ')' -> {$expr.st}
    ; 

ID  :   ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

INT : ('0'..'9')+
  ;

WS  :   (' ' | '\t' | '\r' | '\n')+ {$channel=HIDDEN;}
    ;    
